﻿using System;
using System.Collections.Generic;
using System.Security.Principal;
using System.Web;
using System.Web.SessionState;
using Flash.Internal;
using System.Text;

namespace Flash
{
    public abstract class LoadVarsHandler
        : IHttpHandler, IRequiresSessionState
    {
        private const string DEFAULT_ERROR_FIELD = "$error";
        private const string DEFAULT_EXCEPTION_FIELD = "$exception";

        private HttpContext _context;
        private ResponseParamaterCollection _response;
        private string _errorFieldName;
        private bool _returnError;

        #region Constructors

        public LoadVarsHandler()
        {
            _errorFieldName = DEFAULT_ERROR_FIELD;
            _returnError = true;

            _response = new ResponseParamaterCollection(this);
        }

        public LoadVarsHandler(bool returnError)
            : this()
        {
            _returnError = returnError;
        }

        public LoadVarsHandler(bool returnError, string errorFieldName)
            : this(returnError)
        {
            if (!errorFieldName.IsValidJSVariableName())
            {
                throw new ArgumentException("The errorFieldName argument value must be in JavaScript variable compatible syntax.");
            }
            _errorFieldName = errorFieldName;
        }

        #endregion

        #region Pipeline

        protected abstract void ProcessRequest();

        private void ProcessRequest(HttpContext context)
        {
            bool denied = false;
            _context = context;
            _context.Response.ContentType = "text/plain";

            try
            {
                ProcessRequest();
            }
            catch (System.Runtime.InteropServices.ExternalException)
            {
                throw;
            }
            catch (AccessDeniedException)
            {
                denied = true;
                _context.Response.StatusCode = 401;
            }
            catch (Exception ex)
            {
                if (_returnError)
                {
                    _response.SetErrorState(ex);
                }
                else
                {
                    _context.Response.StatusCode = 500;
                }
            }

            if (!denied)
            {
                WriteResponse();
            }

            _context.Response.End();
        }

        private void WriteResponse()
        {
            bool first = true;
            foreach (var kv in _response)
            {
                if (first)
                {
                    first = false;
                }
                else
                {
                    _context.Response.Write('&');
                }
                _context.Response.Write(string.Format("{0}={1}", kv.Key, HttpUtility.UrlEncode(kv.Value)));
            }
        }

        #endregion

        #region Properties

        /// <summary>
        /// Gets the current Session object provided by ASP.NET.
        /// </summary>
        public HttpSessionState Session
        {
            get
            {
                return _context.Session;
            }
        }

        /// <summary>
        /// Gets the Server object, which is an instance of the System.Web.HttpServerUtility class.
        /// </summary>
        public HttpServerUtility Server
        {
            get
            {
                return _context.Server;
            }
        }

        /// <summary>
        /// Gets information about the user making the page request.
        /// </summary>
        public IPrincipal User
        {
            get
            {
                return _context.User;
            }
        }

        /// <summary>
        /// Gets the System.Web.HttpRequest object for the requested page.
        /// </summary>
        public HttpRequest Request
        {
            get
            {
                return _context.Request;
            }
        }

        /// <summary>
        /// Gets or sets the HTTP character set of the output stream.
        /// </summary>
        public string ResponseEncoding
        {
            get
            {
                return _context.Response.ContentEncoding.EncodingName;
            }
            set
            {
                _context.Response.ContentEncoding = Encoding.GetEncoding(value);
            }
        }

        /// <summary>
        /// Gets the ResponseParamaterCollection object, this object allows you to send HTTP response data to a flash client and contains information about that response.
        /// </summary>
        protected ResponseParamaterCollection FlashResponse
        {
            get
            {
                return _response;
            }
        }

        protected internal HttpContext Context
        {
            get
            {
                return _context;
            }
        }

        #endregion

        #region IHttpHandler Members

        bool IHttpHandler.IsReusable
        {
            get { return false; }
        }

        void IHttpHandler.ProcessRequest(HttpContext context)
        {
            ProcessRequest(context);
        }

        #endregion

        #region Nested Types

        public class ResponseParamaterCollection : IEnumerable<KeyValuePair<string, string>>
        {
            private LoadVarsHandler _handler;
            private Dictionary<string, string> _response;
            private bool _errorState = false;

            public ResponseParamaterCollection(LoadVarsHandler handler)
            {
                _handler = handler;
                _response = new Dictionary<string, string>();
            }

            /// <summary>
            /// Adds a parameter which will be translated later to a property of ActionSctipt's LoadVars object.
            /// </summary>
            /// <param name="key">The name of the variable.</param>
            /// <param name="value">The value of the variable.</param>
            public void AddParameter(string key, string value)
            {
                if (!_errorState)
                {
                    if (ValidateKey(key))
                    {
                        _response.Add(key, value);
                    }
                    else
                    {
                        throw new ArgumentException(string.Format("The key '{0}' already exists.", key));
                    }
                }
            }

            /// <summary>
            /// Sets the ResponseParamaterCollection object to an error state, providing the exception instance.
            /// </summary>
            /// <param name="ex">The exception instance.</param>
            public void SetErrorState(Exception ex)
            {
                SetErrorState(ex.Message, ex);
            }

            /// <summary>
            /// Sets the ResponseParamaterCollection object to an error state, providing the error description.
            /// </summary>
            /// <param name="ex">The error description string.</param>
            public void SetErrorState(string description)
            {
                SetErrorState(description, null);
            }

            private void SetErrorState(string description,Exception ex)
            {
                if (!_errorState)
                {
                    _errorState = true;
                    _response.Clear();

                    if (ex != null)
                    {
                        _response.Add(DEFAULT_EXCEPTION_FIELD, ex.ToString());
                    }

                    _response.Add(_handler._errorFieldName, ex.Message);
                }
            }

            private bool ValidateKey(string key)
            {
                return key.IsValidJSVariableName() && (_handler._returnError && key != _handler._errorFieldName);
            }

            #region IEnumerable Members

            System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
            {
                return _response.GetEnumerator();
            }

            IEnumerator<KeyValuePair<string, string>> IEnumerable<KeyValuePair<string, string>>.GetEnumerator()
            {
                return _response.GetEnumerator();
            }

            #endregion
        }

        #endregion
    }
}
